{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "06432a41-540a-4a55-b3fa-8665032e6127",
   "metadata": {},
   "source": [
    "## Runnable Interface 介绍与使用\n",
    "\n",
    "为了尽可能简化创建自定义链的过程，Langchain 实现了一个 **[Runnable](https://api.python.langchain.com/en/stable/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable)** 协议。\n",
    "\n",
    "许多 LangChain 组件都实现了 Runnable 协议，包括 chat models, LLMs, output parsers, retrievers, prompt templates等等。此外，还有几个用于处理可运行对象的[有用原语](https://python.langchain.com/v0.1/docs/expression_language/primitives/)。\n",
    "\n",
    "Runnable 是一个标准接口，包括：\n",
    "\n",
    "- stream：流式返回生成内容（chunk）\n",
    "- invoke：对输入调用该链\n",
    "- batch：对输入列表调用该链\n",
    "\n",
    "不同组件的输入和输出类型有所差异:\n",
    "\n",
    "| 组件    | 输入类型                                           | 输出类型           |\n",
    "| ------------ | ----------------------------------------------------- | --------------------- |\n",
    "| Prompt       | Dictionary                                            | PromptValue           |\n",
    "| ChatModel    | Single string, list of chat messages or a PromptValue | ChatMessage           |\n",
    "| LLM          | Single string, list of chat messages or a PromptValue | String                |\n",
    "| OutputParser | The output of an LLM or ChatModel                     | Depends on the parser |\n",
    "| Retriever    | Single string                                         | List of Documents     |\n",
    "| Tool         | Single string or dictionary, depending on the tool    | Depends on the tool   |\n",
    "\n",
    "\n",
    "所有 Runnable 对象都显式描述输入和输出 Schema，以检查输入和输出格式：\n",
    "\n",
    "- input_schema：从Runnable的结构自动生成的输入Pydantic模型\n",
    "- output_schema：从Runnable的结构自动生成的输出Pydantic模型 \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f03529a6-80f3-425a-988c-0f1b0dd17d3c",
   "metadata": {},
   "source": [
    "### Input Schema\n",
    "\n",
    "为了演示如何使用，下面我们创建一个超级简单的PromptTemplate + ChatModel链。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "69a3f4fd-e4dc-45ac-a9cf-a36b5791aafc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "model = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "prompt = ChatPromptTemplate.from_template(\"讲个关于 {topic} 的笑话吧\")\n",
    "chain = prompt | model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e5bb871-a240-429b-b622-23a65f5b4af2",
   "metadata": {},
   "source": [
    "#### schema 方法\n",
    "\n",
    "一个描述 Runnable 接受的输入的说明。这是根据任何 Runnable 结构动态生成的 Pydantic 模型。您可以调用 .schema() 来获取 `JSONSchema` 表示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "15a39360-11de-407f-92bc-c372a5a8efb4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'title': 'PromptInput',\n",
       " 'type': 'object',\n",
       " 'properties': {'topic': {'title': 'Topic', 'type': 'string'}},\n",
       " 'required': ['topic']}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看 Chain 的输入类型\n",
    "chain.input_schema.schema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2b8fc874-0a36-404a-a627-72dfd50d719b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'title': 'PromptInput',\n",
       " 'type': 'object',\n",
       " 'properties': {'topic': {'title': 'Topic', 'type': 'string'}},\n",
       " 'required': ['topic']}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看 Prompt 的输入类型（Chain的输入从 Prompt 开始，因此输入类型一致）\n",
    "prompt.input_schema.schema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "3a6077c1-4add-4d0a-a81b-b4b8dba1d950",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'title': 'ChatOpenAIInput',\n",
       " 'anyOf': [{'type': 'string'},\n",
       "  {'$ref': '#/definitions/StringPromptValue'},\n",
       "  {'$ref': '#/definitions/ChatPromptValueConcrete'},\n",
       "  {'type': 'array',\n",
       "   'items': {'anyOf': [{'$ref': '#/definitions/AIMessage'},\n",
       "     {'$ref': '#/definitions/HumanMessage'},\n",
       "     {'$ref': '#/definitions/ChatMessage'},\n",
       "     {'$ref': '#/definitions/SystemMessage'},\n",
       "     {'$ref': '#/definitions/FunctionMessage'},\n",
       "     {'$ref': '#/definitions/ToolMessage'}]}}],\n",
       " 'definitions': {'StringPromptValue': {'title': 'StringPromptValue',\n",
       "   'description': 'String prompt value.',\n",
       "   'type': 'object',\n",
       "   'properties': {'text': {'title': 'Text', 'type': 'string'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'StringPromptValue',\n",
       "     'enum': ['StringPromptValue'],\n",
       "     'type': 'string'}},\n",
       "   'required': ['text']},\n",
       "  'ToolCall': {'title': 'ToolCall',\n",
       "   'type': 'object',\n",
       "   'properties': {'name': {'title': 'Name', 'type': 'string'},\n",
       "    'args': {'title': 'Args', 'type': 'object'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'type': {'title': 'Type', 'enum': ['tool_call'], 'type': 'string'}},\n",
       "   'required': ['name', 'args', 'id']},\n",
       "  'InvalidToolCall': {'title': 'InvalidToolCall',\n",
       "   'type': 'object',\n",
       "   'properties': {'name': {'title': 'Name', 'type': 'string'},\n",
       "    'args': {'title': 'Args', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'error': {'title': 'Error', 'type': 'string'},\n",
       "    'type': {'title': 'Type',\n",
       "     'enum': ['invalid_tool_call'],\n",
       "     'type': 'string'}},\n",
       "   'required': ['name', 'args', 'id', 'error']},\n",
       "  'UsageMetadata': {'title': 'UsageMetadata',\n",
       "   'type': 'object',\n",
       "   'properties': {'input_tokens': {'title': 'Input Tokens', 'type': 'integer'},\n",
       "    'output_tokens': {'title': 'Output Tokens', 'type': 'integer'},\n",
       "    'total_tokens': {'title': 'Total Tokens', 'type': 'integer'}},\n",
       "   'required': ['input_tokens', 'output_tokens', 'total_tokens']},\n",
       "  'AIMessage': {'title': 'AIMessage',\n",
       "   'description': 'Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'ai',\n",
       "     'enum': ['ai'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'example': {'title': 'Example', 'default': False, 'type': 'boolean'},\n",
       "    'tool_calls': {'title': 'Tool Calls',\n",
       "     'default': [],\n",
       "     'type': 'array',\n",
       "     'items': {'$ref': '#/definitions/ToolCall'}},\n",
       "    'invalid_tool_calls': {'title': 'Invalid Tool Calls',\n",
       "     'default': [],\n",
       "     'type': 'array',\n",
       "     'items': {'$ref': '#/definitions/InvalidToolCall'}},\n",
       "    'usage_metadata': {'$ref': '#/definitions/UsageMetadata'}},\n",
       "   'required': ['content']},\n",
       "  'HumanMessage': {'title': 'HumanMessage',\n",
       "   'description': 'Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import HumanMessage, SystemMessage\\n\\n        messages = [\\n            SystemMessage(\\n                content=\"You are a helpful assistant! Your name is Bob.\"\\n            ),\\n            HumanMessage(\\n                content=\"What is your name?\"\\n            )\\n        ]\\n\\n        # Instantiate a chat model and invoke it with the messages\\n        model = ...\\n        print(model.invoke(messages))',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'human',\n",
       "     'enum': ['human'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'example': {'title': 'Example', 'default': False, 'type': 'boolean'}},\n",
       "   'required': ['content']},\n",
       "  'ChatMessage': {'title': 'ChatMessage',\n",
       "   'description': 'Message that can be assigned an arbitrary speaker (i.e. role).',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'chat',\n",
       "     'enum': ['chat'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'role': {'title': 'Role', 'type': 'string'}},\n",
       "   'required': ['content', 'role']},\n",
       "  'SystemMessage': {'title': 'SystemMessage',\n",
       "   'description': 'Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import HumanMessage, SystemMessage\\n\\n        messages = [\\n            SystemMessage(\\n                content=\"You are a helpful assistant! Your name is Bob.\"\\n            ),\\n            HumanMessage(\\n                content=\"What is your name?\"\\n            )\\n        ]\\n\\n        # Define a chat model and invoke it with the messages\\n        print(model.invoke(messages))',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'system',\n",
       "     'enum': ['system'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'}},\n",
       "   'required': ['content']},\n",
       "  'FunctionMessage': {'title': 'FunctionMessage',\n",
       "   'description': 'Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'function',\n",
       "     'enum': ['function'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'}},\n",
       "   'required': ['content', 'name']},\n",
       "  'ToolMessage': {'title': 'ToolMessage',\n",
       "   'description': 'Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import ToolMessage\\n\\n        ToolMessage(content=\\'42\\', tool_call_id=\\'call_Jja7J89XsjrOLA5r!MEOW!SL\\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n    and the full output is passed in to artifact.\\n\\n    .. versionadded:: 0.2.17\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import ToolMessage\\n\\n        tool_output = {\\n            \"stdout\": \"From the graph we can see that the correlation between x and y is ...\",\\n            \"stderr\": None,\\n            \"artifacts\": {\"type\": \"image\", \"base64_data\": \"/9j/4gIcSU...\"},\\n        }\\n\\n        ToolMessage(\\n            content=tool_output[\"stdout\"],\\n            artifact=tool_output,\\n            tool_call_id=\\'call_Jja7J89XsjrOLA5r!MEOW!SL\\',\\n        )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'tool',\n",
       "     'enum': ['tool'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'tool_call_id': {'title': 'Tool Call Id', 'type': 'string'},\n",
       "    'artifact': {'title': 'Artifact'},\n",
       "    'status': {'title': 'Status',\n",
       "     'default': 'success',\n",
       "     'enum': ['success', 'error'],\n",
       "     'type': 'string'}},\n",
       "   'required': ['content', 'tool_call_id']},\n",
       "  'ChatPromptValueConcrete': {'title': 'ChatPromptValueConcrete',\n",
       "   'description': 'Chat prompt value which explicitly lists out the message types it accepts.\\nFor use in external schemas.',\n",
       "   'type': 'object',\n",
       "   'properties': {'messages': {'title': 'Messages',\n",
       "     'type': 'array',\n",
       "     'items': {'anyOf': [{'$ref': '#/definitions/AIMessage'},\n",
       "       {'$ref': '#/definitions/HumanMessage'},\n",
       "       {'$ref': '#/definitions/ChatMessage'},\n",
       "       {'$ref': '#/definitions/SystemMessage'},\n",
       "       {'$ref': '#/definitions/FunctionMessage'},\n",
       "       {'$ref': '#/definitions/ToolMessage'}]}},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'ChatPromptValueConcrete',\n",
       "     'enum': ['ChatPromptValueConcrete'],\n",
       "     'type': 'string'}},\n",
       "   'required': ['messages']}}}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看 Model 的输入类型\n",
    "model.input_schema.schema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e7914531-4e20-4d62-b95d-72930ad6f3f0",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff6d4c11-31b5-4edc-9a4b-611644aea24d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "db2bc6e0-0e6e-497d-bde3-3f24fa9ded77",
   "metadata": {},
   "source": [
    "### Output Schema\n",
    "\n",
    "输出类型仍然可以调用 .schema() 来获取其 `JSONSchema` 表示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0bc07c2a-f9fb-4013-a7c3-6a74c2cb3754",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'title': 'ChatOpenAIOutput',\n",
       " 'anyOf': [{'$ref': '#/definitions/AIMessage'},\n",
       "  {'$ref': '#/definitions/HumanMessage'},\n",
       "  {'$ref': '#/definitions/ChatMessage'},\n",
       "  {'$ref': '#/definitions/SystemMessage'},\n",
       "  {'$ref': '#/definitions/FunctionMessage'},\n",
       "  {'$ref': '#/definitions/ToolMessage'}],\n",
       " 'definitions': {'ToolCall': {'title': 'ToolCall',\n",
       "   'type': 'object',\n",
       "   'properties': {'name': {'title': 'Name', 'type': 'string'},\n",
       "    'args': {'title': 'Args', 'type': 'object'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'type': {'title': 'Type', 'enum': ['tool_call'], 'type': 'string'}},\n",
       "   'required': ['name', 'args', 'id']},\n",
       "  'InvalidToolCall': {'title': 'InvalidToolCall',\n",
       "   'type': 'object',\n",
       "   'properties': {'name': {'title': 'Name', 'type': 'string'},\n",
       "    'args': {'title': 'Args', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'error': {'title': 'Error', 'type': 'string'},\n",
       "    'type': {'title': 'Type',\n",
       "     'enum': ['invalid_tool_call'],\n",
       "     'type': 'string'}},\n",
       "   'required': ['name', 'args', 'id', 'error']},\n",
       "  'UsageMetadata': {'title': 'UsageMetadata',\n",
       "   'type': 'object',\n",
       "   'properties': {'input_tokens': {'title': 'Input Tokens', 'type': 'integer'},\n",
       "    'output_tokens': {'title': 'Output Tokens', 'type': 'integer'},\n",
       "    'total_tokens': {'title': 'Total Tokens', 'type': 'integer'}},\n",
       "   'required': ['input_tokens', 'output_tokens', 'total_tokens']},\n",
       "  'AIMessage': {'title': 'AIMessage',\n",
       "   'description': 'Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'ai',\n",
       "     'enum': ['ai'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'example': {'title': 'Example', 'default': False, 'type': 'boolean'},\n",
       "    'tool_calls': {'title': 'Tool Calls',\n",
       "     'default': [],\n",
       "     'type': 'array',\n",
       "     'items': {'$ref': '#/definitions/ToolCall'}},\n",
       "    'invalid_tool_calls': {'title': 'Invalid Tool Calls',\n",
       "     'default': [],\n",
       "     'type': 'array',\n",
       "     'items': {'$ref': '#/definitions/InvalidToolCall'}},\n",
       "    'usage_metadata': {'$ref': '#/definitions/UsageMetadata'}},\n",
       "   'required': ['content']},\n",
       "  'HumanMessage': {'title': 'HumanMessage',\n",
       "   'description': 'Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import HumanMessage, SystemMessage\\n\\n        messages = [\\n            SystemMessage(\\n                content=\"You are a helpful assistant! Your name is Bob.\"\\n            ),\\n            HumanMessage(\\n                content=\"What is your name?\"\\n            )\\n        ]\\n\\n        # Instantiate a chat model and invoke it with the messages\\n        model = ...\\n        print(model.invoke(messages))',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'human',\n",
       "     'enum': ['human'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'example': {'title': 'Example', 'default': False, 'type': 'boolean'}},\n",
       "   'required': ['content']},\n",
       "  'ChatMessage': {'title': 'ChatMessage',\n",
       "   'description': 'Message that can be assigned an arbitrary speaker (i.e. role).',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'chat',\n",
       "     'enum': ['chat'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'role': {'title': 'Role', 'type': 'string'}},\n",
       "   'required': ['content', 'role']},\n",
       "  'SystemMessage': {'title': 'SystemMessage',\n",
       "   'description': 'Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import HumanMessage, SystemMessage\\n\\n        messages = [\\n            SystemMessage(\\n                content=\"You are a helpful assistant! Your name is Bob.\"\\n            ),\\n            HumanMessage(\\n                content=\"What is your name?\"\\n            )\\n        ]\\n\\n        # Define a chat model and invoke it with the messages\\n        print(model.invoke(messages))',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'system',\n",
       "     'enum': ['system'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'}},\n",
       "   'required': ['content']},\n",
       "  'FunctionMessage': {'title': 'FunctionMessage',\n",
       "   'description': 'Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'function',\n",
       "     'enum': ['function'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'}},\n",
       "   'required': ['content', 'name']},\n",
       "  'ToolMessage': {'title': 'ToolMessage',\n",
       "   'description': 'Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import ToolMessage\\n\\n        ToolMessage(content=\\'42\\', tool_call_id=\\'call_Jja7J89XsjrOLA5r!MEOW!SL\\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n    and the full output is passed in to artifact.\\n\\n    .. versionadded:: 0.2.17\\n\\n    .. code-block:: python\\n\\n        from langchain_core.messages import ToolMessage\\n\\n        tool_output = {\\n            \"stdout\": \"From the graph we can see that the correlation between x and y is ...\",\\n            \"stderr\": None,\\n            \"artifacts\": {\"type\": \"image\", \"base64_data\": \"/9j/4gIcSU...\"},\\n        }\\n\\n        ToolMessage(\\n            content=tool_output[\"stdout\"],\\n            artifact=tool_output,\\n            tool_call_id=\\'call_Jja7J89XsjrOLA5r!MEOW!SL\\',\\n        )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.',\n",
       "   'type': 'object',\n",
       "   'properties': {'content': {'title': 'Content',\n",
       "     'anyOf': [{'type': 'string'},\n",
       "      {'type': 'array',\n",
       "       'items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}]},\n",
       "    'additional_kwargs': {'title': 'Additional Kwargs', 'type': 'object'},\n",
       "    'response_metadata': {'title': 'Response Metadata', 'type': 'object'},\n",
       "    'type': {'title': 'Type',\n",
       "     'default': 'tool',\n",
       "     'enum': ['tool'],\n",
       "     'type': 'string'},\n",
       "    'name': {'title': 'Name', 'type': 'string'},\n",
       "    'id': {'title': 'Id', 'type': 'string'},\n",
       "    'tool_call_id': {'title': 'Tool Call Id', 'type': 'string'},\n",
       "    'artifact': {'title': 'Artifact'},\n",
       "    'status': {'title': 'Status',\n",
       "     'default': 'success',\n",
       "     'enum': ['success', 'error'],\n",
       "     'type': 'string'}},\n",
       "   'required': ['content', 'tool_call_id']}}}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看 Chain 的输出类型\n",
    "chain.output_schema.schema()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc971bb2-8279-4e38-a539-54cc41a5e4a5",
   "metadata": {},
   "source": [
    "### Stream\n",
    "\n",
    "使用 .stream() 方法查看（同步）流式输出结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3cebbeb5-72e0-4606-8383-fa7efa6465bc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当然可以！这里有一个关于程序员的笑话：\n",
      "\n",
      "有一天，一位程序员走进了酒吧，点了一杯酒。酒保问他：“你想要加冰吗？” \n",
      "\n",
      "程序员回答：“不，我只想要一个整数。” \n",
      "\n",
      "酒保困惑地问：“什么是一个整数？” \n",
      "\n",
      "程序员微笑着说：“就是不加冰的。” \n",
      "\n",
      "希望这个笑话能让你笑一笑！"
     ]
    }
   ],
   "source": [
    "for s in chain.stream({\"topic\": \"程序员\"}):\n",
    "    print(s.content, end=\"\", flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "444adacc-ee61-4c41-a2a0-c7a6278a6dea",
   "metadata": {},
   "source": [
    "### Invoke\n",
    "使用 .invoke() 方法单次（同步）调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f8b68201-63a0-48f0-ae40-a835fb889c12",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='当然可以！这是一个关于程序员的笑话：\\n\\n有一天，一个程序员走进一家咖啡馆，点了一杯咖啡。服务员问他：“要加糖吗？”\\n\\n程序员回答说：“不，我只想要一个‘0’或‘1’。”\\n\\n服务员困惑地问：“这是什么意思？”\\n\\n程序员笑着说：“因为我只需要‘甜’或者‘不甜’！” \\n\\n希望这个笑话能让你笑一笑！', response_metadata={'token_usage': {'completion_tokens': 102, 'prompt_tokens': 18, 'total_tokens': 120}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_9722793223', 'finish_reason': 'stop', 'logprobs': None}, id='run-e0cc952f-47ee-49c1-82c4-711145e26be2-0')"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke({\"topic\": \"程序员\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e16f0ec6-f2d6-4b75-ae6c-6e5e685a326b",
   "metadata": {},
   "source": [
    "### Batch\n",
    "使用 .batch() 方法（同步）批量调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "867e73cd-e59e-47d3-8284-d0b97eddc5a9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='当然可以！这里有一个关于程序员的笑话：\\n\\n有一天，程序员走进一家酒吧，点了一杯啤酒。 bartender问他：“你要喝什么类型的啤酒？”程序员回答：“我想要一杯‘Bug-Free’的啤酒！” bartender愣了一下，问道：“那是什么样的啤酒？”程序员笑着说：“当然是一种不会出现任何问题的啤酒，但我知道这绝对不可能！”\\n\\n希望你喜欢这个笑话！', response_metadata={'token_usage': {'completion_tokens': 113, 'prompt_tokens': 18, 'total_tokens': 131}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_9722793223', 'finish_reason': 'stop', 'logprobs': None}, id='run-de7e977b-ec6a-4e62-bb91-40d8b6d76fa2-0'),\n",
       " AIMessage(content='当然可以！这里有一个关于产品经理的笑话：\\n\\n有一天，产品经理、开发者和设计师一起去参加一个团队建设活动。在活动中，他们需要一起搭建一个帐篷。\\n\\n产品经理说：“我们需要先讨论一下用户需求，确保帐篷能满足所有用户的期望。”\\n\\n开发者说：“好吧，但我们必须先确定技术可行性，看看能不能在这个预算内搭建。”\\n\\n设计师则说：“等等，我觉得我们应该先选一个更好看的颜色！”\\n\\n最后，帐篷还是没搭好，但他们却在讨论中达成了共识：下次一定要先搭建一个“最小可行产品”！  \\n\\n希望你喜欢这个笑话！', response_metadata={'token_usage': {'completion_tokens': 157, 'prompt_tokens': 16, 'total_tokens': 173}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_5bd87c427a', 'finish_reason': 'stop', 'logprobs': None}, id='run-7267180f-a443-4979-a0a7-f3d5ae703043-0'),\n",
       " AIMessage(content='当然可以！这里有一个关于测试经理的笑话：\\n\\n有一天，一个测试经理走进咖啡店，看到菜单上写着“咖啡 - 5元”。他立刻对服务员说：“请给我一个咖啡！”\\n\\n服务员问：“要加糖吗？”\\n\\n测试经理回答：“请给我一个没有糖的咖啡。”\\n\\n服务员又问：“要加奶吗？”\\n\\n测试经理严肃地说：“请给我一个没有奶的咖啡。”\\n\\n服务员有些困惑，问：“那您要什么呢？”\\n\\n测试经理微笑着回答：“我只想要一个通过测试的咖啡！”\\n\\n希望这个笑话能让你开心！', response_metadata={'token_usage': {'completion_tokens': 150, 'prompt_tokens': 18, 'total_tokens': 168}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_f33667828e', 'finish_reason': 'stop', 'logprobs': None}, id='run-5d01e9c0-223d-4d45-9d4e-cf2cdfae2f89-0')]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.batch([{\"topic\": \"程序员\"}, {\"topic\": \"产品经理\"}, {\"topic\": \"测试经理\"}])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "99c55997-250a-4603-985b-674f33a39bf9",
   "metadata": {},
   "outputs": [],
   "source": [
    "messages = chain.batch([{\"topic\": \"程序员\"}, {\"topic\": \"产品经理\"}, {\"topic\": \"测试经理\"}])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7648e692-7581-4fc0-a453-52cb0e9484f4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "笑话0:\n",
      "\n",
      "当然可以！这是一个关于程序员的笑话：\n",
      "\n",
      "有一天，一个程序员走进酒吧，看到了一张空桌子。他对酒保说：“请给我一杯水。”  \n",
      "酒保点点头，转身去倒水。  \n",
      "程序员等了好久，酒保还是没有回来。于是他又说：“请再给我一杯水。”  \n",
      "酒保依然没有回应。  \n",
      "最后，程序员忍不住了，站起来大喊：“如果你不来，我就要自己写一个水的获取函数！”\n",
      "\n",
      "希望这个笑话能让你笑一笑！\n",
      "\n",
      "\n",
      "笑话1:\n",
      "\n",
      "当然可以！这是一个关于产品经理的笑话：\n",
      "\n",
      "有一天，产品经理、开发人员和设计师一起坐在会议室里讨论新产品的功能。\n",
      "\n",
      "产品经理说：“我们需要添加一个用户反馈的功能，这样我们可以收集用户的意见。”\n",
      "\n",
      "开发人员皱了皱眉头：“可是这会增加我们的开发时间。”\n",
      "\n",
      "设计师插嘴说：“我觉得用户根本不知道他们想要什么。”\n",
      "\n",
      "产品经理微笑着说：“没关系，我们可以先把这个功能做出来，然后再问他们。”\n",
      "\n",
      "开发人员和设计师对视一眼，异口同声地说：“这听起来像是你们的工作！”\n",
      "\n",
      "希望这个笑话让你开心！\n",
      "\n",
      "\n",
      "笑话2:\n",
      "\n",
      "当然可以！这里有一个关于测试经理的笑话：\n",
      "\n",
      "有一天，一位测试经理走进咖啡店，点了一杯咖啡。咖啡师问他：“您想要加糖吗？”\n",
      "\n",
      "测试经理回答：“请先给我一杯不加糖的咖啡，然后我会测试一下加糖的效果！”\n",
      "\n",
      "咖啡师无奈地摇了摇头：“您真是个彻底的测试经理，连喝咖啡也要进行AB测试！”\n",
      "\n",
      "希望这个笑话能让你开心！\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# 使用 StrOutputParser 来处理 Batch 批量输出\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "output_parser = StrOutputParser()\n",
    "\n",
    "for idx, m in enumerate(messages):\n",
    "    print(f\"笑话{idx}:\\n\")\n",
    "    print(output_parser.invoke(m))\n",
    "    print(\"\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc1d9980-0d5a-4fe7-9a5a-5c6a7fbdfafe",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34e8436e-a647-4562-a2e2-efade07fa090",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "285e2800-354e-4575-9f1c-d2d2a275de3e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "c0bfd2f7-586a-46f8-846a-2f0ea99be1d4",
   "metadata": {},
   "source": [
    "## 异步操作\n",
    "\n",
    "这些方法也有相应的异步方法，应与 `asyncio` 的 `await` 语法一起使用以进行并发操作：\n",
    "\n",
    "- astream：异步地流式返回生成内容（chunk）\n",
    "- ainvoke：异步地对输入调用该链\n",
    "- abatch：异步地对输入列表调用该链\n",
    "- astream_log: 在发生时会返回中间步骤，并且最终返回结果之外。\n",
    "- astream_events: beta 流式传输事件，在 langchain-core 0.1.14 中引入\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85c2d21e-17c7-42e4-905a-9c7e924b44fc",
   "metadata": {},
   "source": [
    "### Async Stream"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "0cbd50d5-8706-4105-8191-41278004c57a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当然可以！这是一个关于程序员的笑话：\n",
      "\n",
      "为什么程序员总是喜欢在海边工作？\n",
      "\n",
      "因为那里有很多“海量数据”！  \n",
      "\n",
      "希望你喜欢这个笑话！如果还想听更多，随时告诉我！"
     ]
    }
   ],
   "source": [
    "async for s in chain.astream({\"topic\": \"程序员\"}):\n",
    "    print(s.content, end=\"\", flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4718496f-ce02-4465-8757-3f6d6e48c063",
   "metadata": {},
   "source": [
    "### Async Invoke"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "dc1fceaf-15aa-44f1-9f31-81d82d64f43a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='当然可以！这是一个关于程序员的笑话：\\n\\n为什么程序员总是喜欢在海边工作？\\n\\n因为他们总是想在“代码”中找到“海洋”！ \\n\\n（“海洋”在这里代表“大量的数据”）希望你喜欢这个笑话！', response_metadata={'token_usage': {'completion_tokens': 60, 'prompt_tokens': 18, 'total_tokens': 78}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_f33667828e', 'finish_reason': 'stop', 'logprobs': None}, id='run-b84ea289-a442-48d6-9319-969535f23133-0')"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "await chain.ainvoke({\"topic\": \"程序员\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b88f80e-0b9d-49b4-a44d-9f26f9da2819",
   "metadata": {},
   "source": [
    "### Async Batch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "75f6493d-0b88-4584-89ac-63b60a21fb37",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='当然可以！这是一个关于程序员的笑话：\\n\\n有一天，程序员去买水果。他走到水果摊前，看见老板在称西瓜。\\n\\n程序员问：“这个西瓜多少钱？”\\n\\n老板回答：“每个10块。”\\n\\n程序员思考了一下，问：“那如果我买两个，能不能给我打个折？”\\n\\n老板笑了笑：“当然可以，两个20块！”\\n\\n程序员愣了一下，接着说：“我明白了，您是只支持‘零和’交易的！”\\n\\n老板一脸懵：“什么是‘零和’交易？”\\n\\n程序员微微一笑：“就是我买两个，您就没得赚了！”\\n\\n希望这个笑话能让你开心！', response_metadata={'token_usage': {'completion_tokens': 156, 'prompt_tokens': 18, 'total_tokens': 174}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_f33667828e', 'finish_reason': 'stop', 'logprobs': None}, id='run-b05d03a7-de24-40cb-b127-bcc7557a9bcc-0'),\n",
       " AIMessage(content='当然可以！这是一个关于产品经理的笑话：\\n\\n有一天，一个产品经理走进一家咖啡店，点了一杯咖啡。店员问他：“要加糖吗？” \\n\\n产品经理回答：“请把这个选项放到用户调查中再决定。” \\n\\n店员无奈地说：“好吧，那我给你一杯无糖的咖啡。” \\n\\n产品经理愣了一下：“等等，这样用户就没有选择了！” \\n\\n店员笑着说：“没关系，反正他们也喝不到！” \\n\\n希望这个笑话能让你开心！', response_metadata={'token_usage': {'completion_tokens': 127, 'prompt_tokens': 16, 'total_tokens': 143}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_f33667828e', 'finish_reason': 'stop', 'logprobs': None}, id='run-d10cb1d9-8a7f-4e91-92b6-14e04d2ddb90-0'),\n",
       " AIMessage(content='当然可以！这里有一个关于测试经理的笑话：\\n\\n有一天，测试经理带着团队去参加一个培训课程。培训师问大家：“你们认为测试的主要目标是什么？”\\n\\n测试经理自信满满地回答：“找到程序中的所有错误！”\\n\\n培训师点点头，然后问：“那你们觉得找到所有错误后会发生什么？”\\n\\n测试经理想了想，严肃地说：“那就得重新开始找下一个项目的错误了！”\\n\\n大家都笑了，因为测试似乎永无止境！', response_metadata={'token_usage': {'completion_tokens': 113, 'prompt_tokens': 18, 'total_tokens': 131}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_f905cf32a9', 'finish_reason': 'stop', 'logprobs': None}, id='run-ef53df81-7f6f-4507-bed8-ad2e8398d962-0')]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "await chain.abatch([{\"topic\": \"程序员\"}, {\"topic\": \"产品经理\"}, {\"topic\": \"测试经理\"}])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "63e40e27-a304-45ae-bc58-1d759e823671",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
